﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CodeGenerator
{
    abstract class AstExpression : AstNode
    {        
    }

    abstract class AstSingleLiteralExpression : AstExpression
    {
        public string Value { get; protected set; }

        public AstSingleLiteralExpression(String val)
        {
            Value = val;
        }
    }

    class AstInteger : AstSingleLiteralExpression
    {
        public AstInteger(string value)
            :base(value)
        { 
        }

        public override void Accept(AstNodeVisitor visitor)
        {
            visitor.Visit(this);
        }
    }

    interface IAstClassField : IAstNode
    {
    }

    class AstId : AstSingleLiteralExpression, IAstClassField
    {
        public AstId(string value)
            : base(value) 
        {
        }

        public override void Accept(AstNodeVisitor visitor)
        {
            visitor.Visit(this);
        }
    }

    class AstArrayType : AstId
    {
        public AstArrayType(AstId b)
            : base(b.Value)
        {
        }

        public override void Accept(AstNodeVisitor visitor)
        {
            visitor.Visit(this);
        }
    }

    class AstStatements : AstStatement
    {
        public List<AstStatement> Statements { get; protected set; }

        public AstStatements(List<AstStatement> statements)
        {
            Statements = statements;
        }

        public override void Accept(AstNodeVisitor visitor)
        {
            if (visitor.Visit(this))
            {
                foreach (AstStatement s in Statements)
                {
                    s.Accept(visitor);
                }
            }
        }
    }

    abstract class AstStatement : AstNode
    {
    }

    class AstMainClass : AstNode
    {
        public AstId ClassName { get; protected set; }
        public List<AstVarDeclaration> FieldDeclarations { get; protected set; }
        public AstEntryPointMainFunctionDecl EntryFunction { get; protected set; }

        public AstMainClass(AstId className, List<AstVarDeclaration> declarations, AstEntryPointMainFunctionDecl mainFunc)
        {
            this.ClassName = className;
            this.FieldDeclarations = declarations;
            this.EntryFunction = mainFunc;
        }

        public override void Accept(AstNodeVisitor visitor)
        {
            if (visitor.Visit(this))
            {
                ClassName.Accept(visitor);

                foreach (AstVarDeclaration d in FieldDeclarations)
                {
                    d.Accept(visitor);
                }

                EntryFunction.Accept(visitor);
            }
        }
    }

    class AstMethodDecl : AstNode
    {
        public AstAccessModifier AccessModifier { get; protected set; }
        public AstStaticModifier StaticModifier { get; protected set; }
        public AstId Type { get; protected set; }
        public AstId FunctionName { get; protected set; }
        public AstFunctionArgumentsList ArgumentsList { get; protected set; }
        public AstStatements Statements { get; protected set; }

        public AstMethodDecl(
                AstAccessModifier accessModifier,
                AstStaticModifier staticModifier,
                AstId type,
                AstId functionName,
                AstFunctionArgumentsList argumentsList,
                AstStatements statements
            )
        {
            this.AccessModifier = accessModifier;
            this.StaticModifier = staticModifier;
            this.Type = type;
            this.FunctionName = functionName;
            this.ArgumentsList = argumentsList;
            this.Statements = statements;
        }

        public override void Accept(AstNodeVisitor visitor)
        {
            if (visitor.Visit(this))
            {
                AccessModifier.Accept(visitor);
                StaticModifier.Accept(visitor);
                Type.Accept(visitor);
                FunctionName.Accept(visitor);
                ArgumentsList.Accept(visitor);
                Statements.Accept(visitor);
            }
        }
    }

    class AstFunctionArgumentsList : AstNode
    {
        public List<AstVarDeclaration> Declarations { get; protected set; }

        public AstFunctionArgumentsList(List<AstVarDeclaration> declarations)
        {
            this.Declarations = declarations;
        }

        public override void Accept(AstNodeVisitor visitor)
        {
            if (visitor.Visit(this))
            {
                foreach (AstVarDeclaration decl in Declarations)
                {
                    decl.Accept(visitor);
                }
            }
        }
    }

    class AstVarDeclaration : AstStatement
    {
        public AstId Type { get; protected set; }
        public AstId Id { get; protected set; }

        public AstVarDeclaration(AstId type, AstId id)
        {
            this.Type = type;
            this.Id = id;
        }

        public override void Accept(AstNodeVisitor visitor)
        {
            if (visitor.Visit(this))
            {
                Type.Accept(visitor);
                Id.Accept(visitor);
            }
        }
    }
    
    class AstAccessModifier : AstNode
    {
        public enum AccessType
        {
            PUBLIC
        }

        public AccessType Access { get; protected set; }

        public AstAccessModifier(AccessType access = AccessType.PUBLIC)
        {
            this.Access = access;
        }

        public override void Accept(AstNodeVisitor visitor)
        {
            visitor.Visit(this);
        }
    }
    
    class AstEntryPointMainFunctionDecl : AstMethodDecl
    {
        public AstEntryPointMainFunctionDecl(
                AstAccessModifier accessModifier,
                AstStaticModifier staticModifier,
                AstId type,
                AstId functionName,
                AstFunctionArgumentsList argumentsList,
                AstStatements statements
            )
            :base(accessModifier, staticModifier, type, functionName, argumentsList, statements)
        {            
        }

        public override void Accept(AstNodeVisitor visitor)
        {
            if (visitor.Visit(this))
            {
                AccessModifier.Accept(visitor);
                StaticModifier.Accept(visitor);
                Type.Accept(visitor);
                FunctionName.Accept(visitor);
                ArgumentsList.Accept(visitor);
                Statements.Accept(visitor);
            }
        }
    }

    class AstStaticModifier : AstNode
    {
        public override void Accept(AstNodeVisitor visitor)
        {
            visitor.Visit(this);
        }
    }
    
    class AstIfStatement : AstStatement
    {
        public AstExpression Condition { get; protected set; }
        public AstStatements ThenStatements { get; protected set; }
        public AstStatements ElseStatementsOpt { get; protected set; }

        public AstIfStatement(AstExpression cond, AstStatements thenStatements, AstStatements elseStatements = null)
        {
            Condition = cond;
            ThenStatements = thenStatements;
            ElseStatementsOpt = elseStatements;
        }

        public override void Accept(AstNodeVisitor visitor)
        {
            if (visitor.Visit(this))
            {
                Condition.Accept(visitor);
                ThenStatements.Accept(visitor);
                if (ElseStatementsOpt != null)
                {
                    ElseStatementsOpt.Accept(visitor);
                }
            }
        }
    }
    
    class AstAssigmentStatement : AstStatement
    {
        public AstExpression LeftOperand { get; protected set; }
        public AstExpression RightOperand { get; protected set; }

        public AstAssigmentStatement(AstExpression left, AstExpression right)
        {
            LeftOperand = left;
            RightOperand = right;
        }

        public override void Accept(AstNodeVisitor visitor)
        {
            if (visitor.Visit(this))
            {
                LeftOperand.Accept(visitor);
                RightOperand.Accept(visitor);
            }
        }
    }

    class AstArrayAccess : AstExpression
    {
        public AstId Id { get; protected set; }
        public AstExpression Indexer { get; protected set; }

        public AstArrayAccess(AstId id, AstExpression indexer)
        {
            Id = id;
            Indexer = indexer;
        }

        public override void Accept(AstNodeVisitor visitor)
        {
            if (visitor.Visit(this))
            {
                Id.Accept(visitor);
                Indexer.Accept(visitor);
            }
        }
    }

    class AstArgumentsList : AstNode
    {
        public List<AstExpression> ExpressionList { get; protected set; }

        public AstArgumentsList(List<AstExpression> list)
        {
            ExpressionList = list;

            if (list == null)
            {
                ExpressionList = new List<AstExpression>();
            }
        }

        public override void Accept(AstNodeVisitor visitor)
        {
            if (visitor.Visit(this))
            {
                foreach (AstExpression expr in ExpressionList)
                {
                    expr.Accept(visitor);
                }
            }
        }
    }

    class AstFunctionCall : AstStatement, IAstClassField
    {
        public AstExpression Expr { get; protected set; }
        public AstArgumentsList ArgumentsList { get; protected set; }

        public AstFunctionCall(AstExpression expr, AstArgumentsList list = null)
        {
            Expr = expr;
            ArgumentsList = list;

            if (list == null)
            {
                ArgumentsList = new AstArgumentsList(null);
            }
        }

        public override void Accept(AstNodeVisitor visitor)
        {
            if (visitor.Visit(this))
            {
                Expr.Accept(visitor);
                ArgumentsList.Accept(visitor);
            }
        }
    }

    class AstConsoleWriteFunctionCall : AstFunctionCall
    {
        public AstInteger IntParam { get; protected set; }

        public AstConsoleWriteFunctionCall(AstDotExpression expr, AstInteger intParam)
            :base(expr, null)
        {
            IntParam = intParam;
        }

        public override void Accept(AstNodeVisitor visitor)
        {
            if (visitor.Visit(this))
            {
                Expr.Accept(visitor);
                IntParam.Accept(visitor);
            }
        }
    }

    class AstFunctionCallWithReturnVal : AstExpression, IAstClassField
    {
        public AstExpression Expr { get; protected set; }
        public AstArgumentsList ArgumentsList { get; protected set; }

        public AstFunctionCallWithReturnVal(AstExpression expr, AstArgumentsList list)
        {
            Expr = expr;
            ArgumentsList = list;
        }

        public override void Accept(AstNodeVisitor visitor)
        {
            if (visitor.Visit(this))
            {
                Expr.Accept(visitor);
                ArgumentsList.Accept(visitor);
            }
        }
    }

    class AstDotExpression : AstExpression
    {
        public AstExpression LeftOperand { get; protected set; }
        public IAstClassField ClassField { get; protected set; }

        public AstDotExpression(AstExpression left, IAstClassField classField)
        {
            LeftOperand = left;
            ClassField = classField;
        }

        public override void Accept(AstNodeVisitor visitor)
        {
            if (visitor.Visit(this))
            {
                LeftOperand.Accept(visitor);
                ClassField.Accept(visitor);
            }
        }
    }

    class AstParenExpression : AstExpression
    {
        public AstExpression Expression;

        public AstParenExpression(AstExpression expr)
        {
            Expression = expr;
        }

        public override void Accept(AstNodeVisitor visitor)
        {
            if (visitor.Visit(this))
            {
                Expression.Accept(visitor);
            }
        }
    }

    class AstWhileStatement : AstStatement
    {
        public AstExpression Condition;
        public AstStatements Statements;

        public AstWhileStatement(AstExpression cond, AstStatements stmts)
        {
            Condition = cond;
            Statements = stmts;
        }

        public override void Accept(AstNodeVisitor visitor)
        {
            if (visitor.Visit(this))
            {
                Condition.Accept(visitor);
                Statements.Accept(visitor);
            }
        }
    }

    class AstErroneousExpression : AstSingleLiteralExpression
    {
        // dummy

        public AstErroneousExpression(string val)
            :base(val)
        {
        }

        public override void Accept(AstNodeVisitor visitor)
        {            
        }
    }

    class AstErroneousStatement : AstStatement
    {
        // dummy

        public AstErroneousStatement()
        {
        }

        public override void Accept(AstNodeVisitor visitor)
        {
        }
    }

    abstract class AstBinaryExpression : AstExpression
    {
        public AstExpression LeftOperand { get; protected set; }
        public AstExpression RightOperand { get; protected set; }

        public AstBinaryExpression(AstExpression left, AstExpression right)
        {
            LeftOperand = left;
            RightOperand = right;
        }
    }

    class AstLogicalExpression : AstBinaryExpression
    {
        public TokenType Operator { get; protected set; }

        public AstLogicalExpression( TokenType op, AstExpression left, AstExpression right)
            :base(left, right)
        {
            Operator = op;
        }

        public override void Accept(AstNodeVisitor visitor)
        {
            if (visitor.Visit(this))
            {
                LeftOperand.Accept(visitor);
                RightOperand.Accept(visitor);
            }
        }
    }

    class AstAddExpression : AstBinaryExpression
    {
        public AstAddExpression(AstExpression left, AstExpression right)
            : base(left, right)
        { }

        public override void Accept(AstNodeVisitor visitor)
        {
            if (visitor.Visit(this))
            {
                LeftOperand.Accept(visitor);
                RightOperand.Accept(visitor);
            }
        }
    }

    class AstMinusExpression : AstBinaryExpression
    {
        public AstMinusExpression(AstExpression left, AstExpression right)
            : base(left, right)
        { }

        public override void Accept(AstNodeVisitor visitor)
        {
            if (visitor.Visit(this))
            {
                LeftOperand.Accept(visitor);
                RightOperand.Accept(visitor);
            }
        }
    }

    class AstMultiplicationExpression : AstBinaryExpression
    {
        public AstMultiplicationExpression(AstExpression left, AstExpression right)
            : base(left, right)
        { }

        public override void Accept(AstNodeVisitor visitor)
        {
            if (visitor.Visit(this))
            {
                LeftOperand.Accept(visitor);
                RightOperand.Accept(visitor);
            }
        }
    }

    class AstDivisionExpression : AstBinaryExpression
    {
        public AstDivisionExpression(AstExpression left, AstExpression right)
            : base(left, right)
        { }

        public override void Accept(AstNodeVisitor visitor)
        {
            if (visitor.Visit(this))
            {
                LeftOperand.Accept(visitor);
                RightOperand.Accept(visitor);
            }
        }
    }
}
